package web

import (
	_const "gitee.com/lstack_light/go-light/internal/pkg/global"
	"gitee.com/lstack_light/go-light/internal/pkg/utils"
	"gitee.com/lstack_light/go-light/pkg/server/respData"
	commonUtils "gitee.com/lstack_light/go-light/pkg/utils"
	"io/ioutil"
	"net/http"
	"path/filepath"
	"reflect"
	"strings"
)

/**
  @author: light
  @since: 2023/8/5
  @desc: 路由控制器
*/

//
//  tree
//  @Description: 路由树
//
type tree struct {
	prefix   string
	part     string
	isWild   bool
	handler  interface{}
	children []*tree
}

func (t *tree) matchChild(part string) *tree {
	for _, child := range t.children {
		if child.part == part || child.isWild {
			return child
		}
	}
	return nil
}

func (t *tree) appendTree(prefix string, parts []string, index int, handler interface{}) {
	if len(parts) == index {
		t.prefix, t.handler = prefix, handler
		return
	}
	part := parts[index]
	if child := t.matchChild(part); nil != child {
		child.appendTree(prefix, parts, index+1, handler)
	} else {
		isWild := part[0] == _const.StarMarkWildcardCharacter || part[0] == _const.TheColonWildcardCharacter
		t.children = append(t.children, &tree{part: part, isWild: isWild, children: make([]*tree, 0)})
		t.children[len(t.children)-1].appendTree(prefix, parts, index+1, handler)
	}
}
func convertRouters(path string) []string {
	parts := make([]string, 0)
	for _, part := range strings.Split(path, _const.TheSlashSeparator) {
		if 0 < len(part) {
			parts = append(parts, part)
			if part[0] == _const.StarMarkWildcardCharacter {
				break
			}
		}
	}
	return parts
}
func (t *tree) MatchingRouter(path string, paramMap *utils.Map) (args []reflect.Value, method reflect.Value) {
	routers := convertRouters(path)
	node := t
	for i, part := range routers {
		if child := node.matchChild(part); child != nil {
			if child.isWild {
				if strings.HasPrefix(child.part, _const.StarMarkWildcard) {
					(*paramMap)[child.part[0:1]] = strings.Join(routers[i:], _const.TheSlashSeparator)
					node = child
					break
				}
				if qMarkIndex := strings.Index(part, _const.QuestionMarkWildcard); qMarkIndex != -1 {
					(*paramMap)[child.part[1:]] = part[:qMarkIndex]
				} else {
					(*paramMap)[child.part[1:]] = part
				}
			}
			node = child
		} else {
			qMarkIndex := strings.Index(part, _const.QuestionMarkWildcard)
			if qMarkIndex == -1 {
				return
			}
			if i == len(routers)-1 && 1 == len(node.children) {
				node = node.children[0]
			}
		}
	}
	method = reflect.ValueOf(node.handler)
	args = make([]reflect.Value, 0)
	numIn := method.Type().NumIn()
	for j := 0; j < numIn; j++ {
		param := method.Type().In(j).Elem()
		args = append(args, reflect.New(param))
	}
	return
}

type methodTrees struct {
	methodTree  map[string]*tree
	routerTable map[string][]string
}

func (mts *methodTrees) getMethodTree(method string) *tree {
	return (*mts).methodTree[method]
}

func (mts *methodTrees) getRouterTable() map[string][]string {
	return (*mts).routerTable
}

func (mts *methodTrees) set(method, path string, handler interface{}) {
	mt := mts.getMethodTree(method)
	if nil == mt {
		mt = &tree{part: _const.TheSlashSeparator}
	}
	mt.appendTree(path, convertRouters(path), 0, handler)
	(*mts).methodTree[method] = mt
}

type Controller struct {
	path   string
	module interface{}
	Router
}

//
// addRouter
//  @Description: 注册路由
//  @receiver Router 路由分组指针
//  @param method 请求方式
//  @param path 请求路由
//  @param handler 请求处理函数
//  @return Router 路由接口
//
func (c *Controller) addRouter(method, path string, handler interface{}) {
	if !commonUtils.RegexMatch("^(GET|POST|PUT|DELETE|HEAD|PATCH|OPTIONS|CONNECT|TRACE)$", method) {
		panic("[" + method + "]请求方式暂不支持")
	}
	path = filepath.ToSlash(filepath.Join(c.path, path))
	defaultEngine := InitEngine()
	defaultEngine.routerTable[method] = append(defaultEngine.routerTable[method], path)
	defaultEngine.set(method, path, handler)
}

type Router interface {
	Get(path string, handler interface{})
	Post(path string, handler interface{})
	Put(path string, handler interface{})
	Delete(path string, handler interface{})
	Head(path string, handler interface{})
	Patch(path string, handler interface{})
	Options(path string, handler interface{})
	Connect(path string, handler interface{})
	Trace(path string, handler interface{})
	StaticFile(path, rootDir string)
}

func NewController(path string, module interface{}, middleWares ...*MiddleWare) *Controller {
	if !strings.HasPrefix(path, _const.TheSlashSeparator) {
		panic("设置路由必须以'/'开头")
	}
	c := &Controller{path: path, module: module}
	c.useMiddleware(middleWares...)
	return c
}

func (c *Controller) useMiddleware(middleWares ...*MiddleWare) {
	initEngine := InitEngine()
	for _, middleWare := range middleWares {
		exits := false
		for _, mw := range initEngine.MiddleWares {
			if middleWare.Key == mw.Key {
				exits = true
				break
			}
		}
		if !exits {
			initEngine.MiddleWares = append(initEngine.MiddleWares, *middleWare)
		}
		initEngine.Interceptor[middleWare.Key] = append(initEngine.Interceptor[middleWare.Key], c.path)
	}
}

func (c *Controller) Get(path string, handler interface{}) {
	c.addRouter(http.MethodGet, path, handler)
}

func (c *Controller) Post(path string, handler interface{}) {
	c.addRouter(http.MethodPost, path, handler)
}

func (c *Controller) Put(path string, handler interface{}) {
	c.addRouter(http.MethodPut, path, handler)
}

func (c *Controller) Delete(path string, handler interface{}) {
	c.addRouter(http.MethodDelete, path, handler)
}

func (c *Controller) Head(path string, handler interface{}) {
	c.addRouter(http.MethodHead, path, handler)
}

func (c *Controller) Patch(path string, handler interface{}) {
	c.addRouter(http.MethodPatch, path, handler)
}

func (c *Controller) Options(path string, handler interface{}) {
	c.addRouter(http.MethodOptions, path, handler)
}

func (c *Controller) Connect(path string, handler interface{}) {
	c.addRouter(http.MethodConnect, path, handler)
}

func (c *Controller) Trace(path string, handler interface{}) {
	c.addRouter(http.MethodTrace, path, handler)
}

func (c *Controller) StaticFile(path, rootDir string) {
	if !strings.HasSuffix(rootDir, _const.TheSlashSeparator) {
		rootDir = rootDir + _const.TheSlashSeparator
	}
	s := staticFile{rootDir: rootDir}
	c.addRouter(http.MethodGet, path, s.download)
}

type File struct {
	FilePath string `uri:"*"`
}

type staticFile struct {
	rootDir string
}

func (s *staticFile) download(file *File) *respData.Response {
	if strings.HasPrefix(file.FilePath, _const.TheSlashSeparator) {
		file.FilePath = file.FilePath[1:]
	}
	path := s.rootDir + file.FilePath
	content, _ := ioutil.ReadFile(path)
	split := strings.Split(path, _const.TheSlashSeparator)
	return respData.StaticFileResponse(split[len(split)-1], content)
}
